﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Linq.Expressions;
using System.Linq.Dynamic;
using System.Collections.Specialized;
using System.Diagnostics;

namespace FastLambda.Benchmark
{
    class Program
    {
        static void Main(string[] args)
        {
            var arguments = ParseArguments(args);

            if (arguments["task"] == "perf-test")
            {
                PerfTest(arguments);
            }
            else if (arguments["task"] == "compile-invoke-test")
            {
                CompileInvokeTest(arguments);
            }
            else
            {
                GenerateExpressions(arguments);
            }

            Console.WriteLine("Press enter to continue...");
            Console.ReadLine();
        }

        private static NameValueCollection ParseArguments(string[] args)
        {
            var arguments = new NameValueCollection();
            foreach (string a in args)
            {
                int index = a.IndexOf(":");
                string argName = a.Substring(1, index - 1);
                string argValue = a.Substring(index + 1);

                arguments.Add(argName.ToLowerInvariant(), argValue);
            }

            return arguments;
        }

        private static void WriteSingleExpression(
            TextWriter writer, Random random, int opCount)
        {
            string ops = "+-*/";

            writer.Write(random.Next(100));
            while (opCount-- > 0)
            {
                writer.Write(" ");
                writer.Write(ops[random.Next(4)]);
                writer.Write(" ");
                writer.Write(random.Next(1, 100));
            }
            writer.WriteLine();
        }

        private static void GenerateExpressions(NameValueCollection args)
        {
            string output = args["out"] ?? @"expressions\expr_{0}.txt";
            int min = Int32.Parse(args["min"] ?? "1");
            int max = Int32.Parse(args["max"] ?? (min + 19).ToString());
            int repeat = Int32.Parse(args["repeat"] ?? "10");

            Random random = new Random(DateTime.Now.Millisecond);
            for (int opCount = min; opCount <= max; opCount++)
            {
                string file = String.Format(output, opCount);
                using (var writer = new StreamWriter(File.Open(file, FileMode.Create)))
                {
                    for (int i = 0; i < repeat; i++)
                    {
                        WriteSingleExpression(writer, random, opCount);
                    }
                }
            }
        }

        static void CompileInvokeTest(NameValueCollection args)
        {
            string input = args["in"] ?? @"expressions\expr_{0}.txt";
            string output = args["out"] ?? @"CompileInvokeTest_output.txt";
            int repeat = Int32.Parse(args["repeat"] ?? "1000");
            int min = Int32.Parse(args["min"] ?? "1");
            int max = Int32.Parse(args["max"] ?? (min + 19).ToString());

            var dictExprs = new Dictionary<int, List<Expression>>();

            for (int opCount = min; opCount <= max; opCount++)
            {
                string file = String.Format(input, opCount);
                dictExprs[opCount] = File.ReadAllLines(file).Select(
                    s => DynamicExpression.Parse(null, s)).ToList();
            }

            using (var writer = new StreamWriter(File.Open(output, FileMode.Create)))
            {
                writer.WriteLine("OpCount\tCompile\tInvoke");

                for (int opCount = min; opCount <= max; opCount++)
                {
                    Stopwatch compileWatch = new Stopwatch();
                    Stopwatch invokeWatch = new Stopwatch();

                    for (int i = 0; i < repeat; i++)
                    {
                        dictExprs[opCount].ForEach(expr =>
                        {
                            compileWatch.Start();
                            var lambda = Expression.Lambda(expr);
                            var func = lambda.Compile();
                            compileWatch.Stop();

                            invokeWatch.Start();
                            func.DynamicInvoke(null);
                            invokeWatch.Stop();
                        });
                    }

                    writer.WriteLine(opCount + "\t" + compileWatch.ElapsedMilliseconds + "\t" + invokeWatch.ElapsedMilliseconds);
                }
            }
        }

        static void PerfTest(NameValueCollection args)
        {
            string input = args["in"] ?? @"expressions\expr_{0}.txt";
            string output = args["out"] ?? @"PerfTest_output.txt";
            int repeat = Int32.Parse(args["repeat"] ?? "1000");
            int min = Int32.Parse(args["min"] ?? "1");
            int max = Int32.Parse(args["max"] ?? (min + 19).ToString());

            var dictExprs = new Dictionary<int, List<Expression>>();

            for (int opCount = min; opCount <= max; opCount++)
            {
                string file = String.Format(input, opCount);
                dictExprs[opCount] = File.ReadAllLines(file).Select(
                    s => DynamicExpression.Parse(null, s)).ToList();
            }

            Evaluator evaluator = new Evaluator();
            CacheEvaluator cacheEvaluator = new CacheEvaluator();
            FastEvaluator fastEvaluator = new FastEvaluator();

            using (var writer = new StreamWriter(File.Open(output, FileMode.Create)))
            {
                writer.WriteLine("OpCount\tNormal\tCache\tFast");

                for (int opCount = min; opCount <= max; opCount++)
                {
                    var expressions = dictExprs[opCount];
                    expressions.ForEach(exp => evaluator.Eval(exp));
                    expressions.ForEach(exp => fastEvaluator.Eval(exp));
                    if (opCount <= 3) expressions.ForEach(exp => cacheEvaluator.Eval(exp));

                    writer.Write(opCount);

                    // Evaluator
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < repeat; i++)
                    {
                        expressions.ForEach(exp => evaluator.Eval(exp));
                    }
                    watch.Stop();
                    writer.Write("\t" + watch.ElapsedMilliseconds);

                    // CacheEvaluator
                    if (opCount <= 3)
                    {
                        watch.Reset();
                        watch.Start();
                        for (int i = 0; i < repeat; i++)
                        {
                            expressions.ForEach(exp => cacheEvaluator.Eval(exp));
                        }
                        watch.Stop();
                        writer.Write("\t" + watch.ElapsedMilliseconds);
                    }
                    else
                    {
                        writer.Write("\t");
                    }

                    // FastEvaluator
                    watch.Reset();
                    watch.Start();
                    for (int i = 0; i < repeat; i++)
                    {
                        expressions.ForEach(exp => fastEvaluator.Eval(exp));
                    }
                    watch.Stop();
                    writer.Write("\t" + watch.ElapsedMilliseconds);

                    writer.WriteLine();
                }
            }
        }

    }
}
